/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hop.pipeline.transforms.webservices.wsdl;

import com.ibm.wsdl.extensions.soap12.SOAP12BodyImpl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javax.wsdl.Binding;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.Port;
import javax.wsdl.extensions.ElementExtensible;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap.SOAPBody;
import javax.wsdl.extensions.soap.SOAPHeader;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.wsdl.extensions.soap12.SOAP12Address;
import javax.wsdl.extensions.soap12.SOAP12Binding;
import javax.wsdl.extensions.soap12.SOAP12Header;
import javax.wsdl.extensions.soap12.SOAP12Operation;
import org.apache.hop.core.exception.HopException;

/** Utilities for getting extensibility elements. */
final class WsdlUtils {

  // extensibility element names
  private static final String SOAP_PORT_ADDRESS_NAME = "address";
  private static final String SOAP_BINDING_ELEMENT_NAME = "binding";
  private static final String SOAP_BODY_ELEMENT_NAME = "body";
  private static final String SOAP_HEADER_ELEMENT_NAME = "header";
  private static final String SOAP_OPERATION_ELEMENT_NAME = "operation";

  // return values
  private static final String SOAP_BINDING_DEFAULT = "document";

  // schema names
  protected static final String ELEMENT_FORM_DEFAULT_ATTR = "elementFormDefault";
  protected static final String ANY_TAG_NAME = "any";
  protected static final String TARGET_NAMESPACE_ATTR = "targetNamespace";
  protected static final String ELEMENT_FORM_QUALIFIED = "qualified";
  protected static final String ELEMENT_NAME = "element";
  protected static final String COMPLEX_TYPE_NAME = "complexType";
  protected static final String SCHEMA_ELEMENT_NAME = "schema";
  protected static final String SEQUENCE_TAG_NAME = "sequence";
  protected static final String SIMPLE_TYPE_NAME = "simpleType";
  protected static final String NAME_ATTR = "name";
  protected static final String ELEMENT_TYPE_ATTR = "type";
  protected static final String ELEMENT_REF_ATTR = "ref";
  protected static final String MAXOCCURS_ATTR = "maxOccurs";
  protected static final String MINOCCURS_ATTR = "minOccurs";

  /**
   * Get the SOAP address location for the specified port.
   *
   * @param p A WSDL Port instance.
   * @return The SOAP address URI.
   */
  protected static String getSOAPAddress(Port p) {
    ExtensibilityElement e =
        findExtensibilityElement((ElementExtensible) p, SOAP_PORT_ADDRESS_NAME);
    if (e instanceof SOAP12Address) {
      return ((SOAP12Address) e).getLocationURI();
    } else if (e instanceof SOAPAddress) {
      return ((SOAPAddress) e).getLocationURI();
    }

    return null;
  }

  /**
   * Get the SOAPBinding style for the specified WSDL Port.
   *
   * @param binding A WSDL Binding instance.
   * @return String either 'document' or 'rpc', if not found in WSDL defaults to 'document'.
   */
  protected static String getSOAPBindingStyle(Binding binding) throws HopException {
    String style = SOAP_BINDING_DEFAULT;
    ExtensibilityElement soapBindingElem =
        findExtensibilityElement((ElementExtensible) binding, SOAP_BINDING_ELEMENT_NAME);

    if (soapBindingElem != null) {
      if (soapBindingElem instanceof SOAP12Binding) {
        style = ((SOAP12Binding) soapBindingElem).getStyle();
      } else if (soapBindingElem instanceof SOAPBinding) {
        style = ((SOAPBinding) soapBindingElem).getStyle();
      } else {
        throw new HopException(
            "Binding type "
                + soapBindingElem
                + " encountered. The Web Service Lookup transform only supports SOAP Bindings!");
      }
    }
    return style;
  }

  /**
   * Get the SOAP Use type for the specified operation.
   *
   * @param binding A WSDL Binding instance.
   * @param operationName The name of the operation.
   * @return Either 'literal' or 'encoded'.
   * @throws RuntimeException If the use type cannot be determined.
   */
  protected static String getSOAPBindingUse(Binding binding, String operationName) {

    BindingOperation bindingOperation = binding.getBindingOperation(operationName, null, null);

    if (bindingOperation == null) {
      throw new IllegalArgumentException("Can not find operation: " + operationName);
    }

    // first try getting the use setting from the input message
    BindingInput bindingInput = bindingOperation.getBindingInput();
    if (bindingInput != null) {
      ExtensibilityElement soapBodyElem =
          WsdlUtils.findExtensibilityElement(
              (ElementExtensible) bindingInput, SOAP_BODY_ELEMENT_NAME);
      if (soapBodyElem != null) {
        if (soapBodyElem instanceof SOAP12BodyImpl) {
          return ((SOAP12BodyImpl) soapBodyElem).getUse();
        } else {
          return ((SOAPBody) soapBodyElem).getUse();
        }
      }
    }

    // if there was no input message try getting the use from the output message
    BindingOutput bindingOutput = bindingOperation.getBindingOutput();
    if (bindingOutput != null) {
      ExtensibilityElement soapBodyElem =
          WsdlUtils.findExtensibilityElement(
              (ElementExtensible) bindingOutput, SOAP_BODY_ELEMENT_NAME);
      if (soapBodyElem != null) {
        if (soapBodyElem instanceof SOAP12BodyImpl) {
          return ((SOAP12BodyImpl) soapBodyElem).getUse();
        } else {
          return ((SOAPBody) soapBodyElem).getUse();
        }
      }
    }

    throw new RuntimeException("Unable to determine SOAP use for operation: " + operationName);
  }

  /**
   * Get the Soap Action URI from the operation's soap:operation extensiblity element.
   *
   * @param operation A WSDL Operation.
   * @return Soap action URI as string, null if not defined.
   */
  protected static String getSOAPAction(BindingOperation operation) {
    ExtensibilityElement e =
        findExtensibilityElement((ElementExtensible) operation, SOAP_OPERATION_ELEMENT_NAME);
    if (e != null) {
      if (e instanceof SOAP12Operation) {
        return ((SOAP12Operation) e).getSoapActionURI();
      } else {
        return ((SOAPOperation) e).getSoapActionURI();
      }
    }
    return null;
  }

  /**
   * Determine if this parameter has a parameter style of WRAPPED. It does if the <tt>name</tt>
   * attribute of the part is NOT the same as its operation name.
   *
   * @param operationName Name of the part's operation.
   * @param outputParam true if this is an output parameter.
   * @param messagePartName Name of the message part.
   * @return true if parameter style is wrapped.
   */
  protected static boolean isWrappedParameterStyle(
      String operationName, boolean outputParam, String messagePartName) {

    if (outputParam) {
      if (messagePartName.equals(operationName + "Response")) {
        return false;
      }
    } else {
      if (messagePartName.equals(operationName)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Build a HashSet of SOAP header names for the specified operation and binding.
   *
   * @param binding WSDL Binding instance.
   * @param operationName Name of the operation.
   * @return HashSet of soap header names, empty set if no headers present.
   */
  protected static HashSet<String> getSOAPHeaders(Binding binding, String operationName) {

    List<ExtensibilityElement> headers = new ArrayList<>();
    BindingOperation bindingOperation = binding.getBindingOperation(operationName, null, null);
    if (bindingOperation == null) {
      throw new IllegalArgumentException("Can not find operation: " + operationName);
    }

    BindingInput bindingInput = bindingOperation.getBindingInput();
    if (bindingInput != null) {
      headers.addAll(
          WsdlUtils.findExtensibilityElements(
              (ElementExtensible) bindingInput, SOAP_HEADER_ELEMENT_NAME));
    }

    BindingOutput bindingOutput = bindingOperation.getBindingOutput();
    if (bindingOutput != null) {
      headers.addAll(
          WsdlUtils.findExtensibilityElements(
              (ElementExtensible) bindingOutput, SOAP_HEADER_ELEMENT_NAME));
    }

    HashSet<String> headerSet = new HashSet<>(headers.size());
    for (ExtensibilityElement element : headers) {
      if (element instanceof SOAP12Header) {
        headerSet.add(((SOAP12Header) element).getPart());
      } else {
        headerSet.add(((SOAPHeader) element).getPart());
      }
    }

    return headerSet;
  }

  /**
   * Find the specified extensibility element, if more than one with the specified name exists in
   * the list, return the first one found.
   *
   * @param extensibleElement WSDL type which extends ElementExtensible.
   * @param elementType Name of the extensiblity element to find.
   * @return ExtensibilityElement The ExtensiblityElement, if not found return null.
   */
  protected static ExtensibilityElement findExtensibilityElement(
      ElementExtensible extensibleElement, String elementType) {

    List<ExtensibilityElement> extensibilityElements = extensibleElement.getExtensibilityElements();
    if (extensibilityElements != null) {
      for (ExtensibilityElement element : extensibilityElements) {
        if (element.getElementType().getLocalPart().equalsIgnoreCase(elementType)) {
          return element;
        }
      }
    }
    return null;
  }

  /**
   * Find all of the extensibility elements with the specified name.
   *
   * @param extensibleElement WSDL type which extends ElementExtensible.
   * @param elementType Name of the extensibility element to find.
   * @return List of ExtensibilityElements, may be empty.
   */
  protected static List<ExtensibilityElement> findExtensibilityElements(
      ElementExtensible extensibleElement, String elementType) {

    List<ExtensibilityElement> elements = new ArrayList<>();
    List<ExtensibilityElement> extensibilityElements = extensibleElement.getExtensibilityElements();

    if (extensibilityElements != null) {
      for (ExtensibilityElement element : extensibilityElements) {
        if (element.getElementType().getLocalPart().equalsIgnoreCase(elementType)) {
          elements.add(element);
        }
      }
    }
    return elements;
  }

  /**
   * @param port
   * @return
   */
  protected static boolean isSoapPort(Port port) {
    return getSOAPAddress(port) != null;
  }
}
